package ua.eug.land3;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;

public class Terrain {
	private static final int VERTEX_SIZE = (3+3+1); // pos + norm + color
	public Mesh mesh;
	private final int sizeX;
	private final int sizeY;
	private float[] vertices;
	private short[] triangles;
	private Array<Vector3> triStore = new Array<Vector3>();
	
	public Terrain(int sX, int sY) {
		sizeX = sX;
		sizeY = sY;
		vertices = new float[sizeX*sizeY*VERTEX_SIZE];
		for (int i = 0; i<sizeY; i++) {
			for (int j = 0; j<sizeX; j++) {
				// coord
				float fi = (float) i;
				float fj = (float) j;
				vertices[(i*sizeX+j)*VERTEX_SIZE] = ((i%2==1) ? (fj+.5f) : fj ) - sizeX/2;
				vertices[(i*sizeX+j)*VERTEX_SIZE+1] = fi - sizeY/2;
				vertices[(i*sizeX+j)*VERTEX_SIZE+2] = (float) (Math.random()+0.5);
				// normal
				vertices[(i*sizeX+j)*VERTEX_SIZE+3] = 0;
				vertices[(i*sizeX+j)*VERTEX_SIZE+4] = 0;
				vertices[(i*sizeX+j)*VERTEX_SIZE+5] = 1;
				// color
				vertices[(i*sizeX+j)*VERTEX_SIZE+6] = Color.toFloatBits(128, 255, 128, 255);
			
				
				if(i%2==0) { 
					if (j<sizeX-1) {
						if (i < sizeY-1) {
							// triangle up, dir NE 
							Vector3 ne = new Vector3( i*sizeX+j, (i+1)*sizeX+j, i*sizeX+j+1);
							triStore.add(ne);
						}
						if (i > 1) {
							// triangle DOWN, dir SE
							Vector3 se = new Vector3(i*sizeX+j, i*sizeX+j+1, (i-1)*sizeX+j);
							triStore.add(se);
						}
					}
				} else {
					if (j>0) {
						// triangle down, dir SW
						Vector3 sw = new Vector3(i*sizeX+j, (i-1)*sizeX+j, i*sizeX+j-1);
						triStore.add(sw);
						
						if (i < sizeY-1) {
							// triangle UP, dir NW
							Vector3 nw = new Vector3(i*sizeX+j, i*sizeX+j-1, (i+1)*sizeX+j);
							triStore.add(nw);
						}
					}
				}
			}
		}

        mesh = new Mesh(false, sizeX*sizeY, triStore.size*3, new VertexAttribute(Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE)
			,new VertexAttribute(Usage.Normal, 3, ShaderProgram.NORMAL_ATTRIBUTE)
			,new VertexAttribute(Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE)
		);
        
        normalize();
        
        mesh.setVertices(vertices);
        
        triangles = new short[triStore.size*3];
        int pos = 0;
        for (Vector3 v : triStore) {
        	triangles[pos++] = (short) v.x;
        	triangles[pos++] = (short) v.y;
        	triangles[pos++] = (short) v.z;
        }
        mesh.setIndices(triangles);

	}

	private float getZ(int x, int y) {
		return vertices[(y*sizeX+x)*VERTEX_SIZE+2];
	}
	
	private void normalize() {
		// https://stackoverflow.com/questions/6656358/calculating-normals-in-a-triangle-mesh/21660173
		for (int i = 1; i<sizeY-1; i++) {
			for (int j = 1; j<sizeX-1; j++) {
				float zleft = getZ(j-1,i);
				float zright = getZ(j+1,i);
				float zupright = getZ(j+1,i+1);
				float zup = getZ(j,i+1);
				float zdownleft = getZ(j,i-1);
				float zdown = getZ(j+1,i-1);	
				if (i%2==0) {
					zupright = getZ(j,i+1);
					zup = getZ(j-1,i+1);
					zdownleft = getZ(j-1,i-1);
					zdown = getZ(j,i-1);
				}
				Vector3 tst = new Vector3(						
						//(2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown)
						2*( zleft - zright) - zupright + zdownleft
						+ zup - zdown,
						//(2*(Zdown - Zup)    + Zupright + Zdownleft - Zup - Zleft)
						2*(zdown - zup)    + zupright + zdownleft - zup - zleft,
						6);
				tst.nor();
				vertices[(i*sizeX+j)*VERTEX_SIZE+3] = tst.x;
				vertices[(i*sizeX+j)*VERTEX_SIZE+4] = tst.y;
				vertices[(i*sizeX+j)*VERTEX_SIZE+5] = tst.z;
			}
		}
	}
}
